/* 
 * Cashbox is the legal property of its developers, whose names are listed in the copyright file included
 * with this source distribution.
 * 
 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
 * General Public License as published by the Free Software Foundation; either version 2 of the License,
 * or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
 * Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with this program; if not,
 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#import <Cashbox/CBTransaction.h>
#import <Cashbox/CBAccount.h>
#import <Cashbox/CBAttribute.h>
#import <Cashbox/CBAttributeInfo.h>
#import <Cashbox/CBAccountExceptions.h>

static void *CBTransferChangeContext = @"CBTransferChangeContext";

@interface CBTransaction (CoreDataGeneratedPrimitiveAccessors)
- (NSDate *)primitiveDate;
- (void)setPrimitiveDate:(NSDate *)value;
- (NSNumber *)primitiveState;
- (void)setPrimitiveState:(NSNumber *)value;
- (NSNumber *)primitiveTotal;
- (void)setPrimitiveTotal:(NSNumber *)value;
- (CBTransaction *)primitiveTransfer;
- (void)setPrimitiveTransfer:(CBTransaction *)value;
@end

@interface CBTransaction ()
@property (retain, readwrite) CBAccount *account;
@property BOOL ignoresValueChanges;
@property BOOL ignoresTransferChange;
@end

@interface CBTransaction (Private)
- (void)setupTransferObservers;
- (CBAttribute *)attributeForRoleType:(CBRoleType)role;
- (void)setTransferValue:(id)value forKey:(NSString *)key;
@end

@implementation CBTransaction

+ (void)initialize {
	if ([self class] != [CBTransaction class]) {
		CBAttributeInfo *attributeInfo = [(Class)self attributeInfo];
		NSMutableArray *currencies = [NSMutableArray array];
		NSEnumerator *attributes = [[attributeInfo attributes] objectEnumerator];
		CBAttribute *attribute;
		while (attribute = [attributes nextObject]) {
			if ([attribute type] == CBCurrencyAttribute &&
				!([attribute options] & CBReadonlyAttribute)) {
				[currencies addObject:[attribute name]];
			}
		}

		NSArray *internalTotal = [NSArray arrayWithObject:@"internalTotal"];
		[self setKeys:currencies triggerChangeNotificationsForDependentKey:@"internalTotal"];
		[self setKeys:internalTotal triggerChangeNotificationsForDependentKey:@"total"];
	}	
}

+ (NSArray *)standardAttributes {
	return [NSArray arrayWithObjects:
			[CBAttribute attributeWithName:@"date"
							 localizedName:FSLocalizedString(@"Date", nil)
									  type:CBDateAttribute
								   options:CBNoOptions
								  role:CBDateRole
									 links:CBDateRole
								   orderID:@"01"],
			[CBAttribute attributeWithName:@"state"
							 localizedName:FSLocalizedString(@"State", nil)
									  type:CBMixedAttribute
								   options:CBNoOptions
								  role:CBNoRole
									 links:CBNoRole
								   orderID:@"00"],
			[CBAttribute attributeWithName:@"label"
							 localizedName:FSLocalizedString(@"Label", nil)
									  type:CBLabelAttribute
								   options:CBNoOptions
								  role:CBNoRole
									 links:CBNoRole
								   orderID:@"91"],
			[CBAttribute attributeWithName:@"total"
							 localizedName:FSLocalizedString(@"Total", nil)
									  type:CBCurrencyAttribute
								   options:CBReadonlyAttribute
								  role:CBNoRole
									 links:CBNoRole
								   orderID:@"90"],
			nil];
}

- (id)initAndInsertIntoAccount:(CBAccount *)account {
	if ([[self class] isEqualTo:[CBTransaction class]]) {
		[NSException raise:@"CBImplementationError"
					format:@"Attemt to instantiate abstract class, CBTransaction, directly."];
	}
	if ((self = [super initWithEntity:[[self class] entity]
	   insertIntoManagedObjectContext:[account managedObjectContext]])) {
		[self setAccount:account];
	}
	return self;
}	

- (void)awakeFromFetch {
	[super awakeFromFetch]; // must be first
	[self setupTransferObservers];
}

- (void)awakeFromInsert {
	[super awakeFromInsert]; // must be first
	[self setTotal:0];
	[self setDate:[NSDate date]];
	[self setState:CBInitialState];
	[self setupTransferObservers];
}

- (void)setupTransferObservers {
	CBAttributeInfo *attributeInfo = [[self class] attributeInfo];

	[self addObserver:self
		   forKeyPath:[[attributeInfo attributeForRole:CBPositiveCashflowRole] name]
			  options:NSKeyValueObservingOptionNew
			  context:CBTransferChangeContext];
	
	[self addObserver:self
		   forKeyPath:[[attributeInfo attributeForRole:CBNegativeCashflowRole] name]
			  options:NSKeyValueObservingOptionNew
			  context:CBTransferChangeContext];
	
	NSEnumerator *attributeEnumerator = [[attributeInfo attributes] objectEnumerator];
	CBAttribute *attribute;
	while (attribute = [attributeEnumerator nextObject]) {
		if ([attribute links] != CBNoRole) {
			[self addObserver:self
				   forKeyPath:[attribute name]
					  options:NSKeyValueObservingOptionNew
					  context:CBTransferChangeContext];
		}
	}
}

- (long long)internalTotal {
	[NSException raise:@"CBImplementationError"
				format:@"CBTransaction is an abstract class.  You must subclass and override internalTotal."];
	return 0;
}

- (void)observeValueForKeyPath:(NSString *)keyPath
					  ofObject:(id)object
						change:(NSDictionary *)change
					   context:(void *)context {
	if (context == CBTransferChangeContext && [self transfer]) {
		if (![self ignoresValueChanges]) {
			[self setTransferValue:[change objectForKey:NSKeyValueChangeNewKey] forKey:keyPath];
		}
	}
}

- (void)setTransferValue:(id)value forKey:(NSString *)key {
	if ([self transfer]) {
		BOOL ignores = [[self transfer] ignoresValueChanges];
		[[self transfer] setIgnoresValueChanges:YES];
		
		CBAttribute *attribute = [[[self class] attributeInfo] attributeWithName:key];
		[[self transfer] setValue:value forRole:[attribute links]];
		
		[[self transfer] setIgnoresValueChanges:ignores];		
	}
}

- (CBState)state {
    NSNumber * tmpValue;
    [self willAccessValueForKey:@"state"];
    tmpValue = [self primitiveState];
    [self didAccessValueForKey:@"state"];
    return (tmpValue == nil) ? 0 : [tmpValue intValue];
}

- (void)setState:(CBState)state {
	NSNumber *temp = [[NSNumber alloc] initWithInt:state];
    [self willChangeValueForKey:@"state"];
    [self setPrimitiveState:temp];
    [self didChangeValueForKey:@"state"];
	[temp release];
}

@dynamic date;
@dynamic label;
@dynamic account;
@synthesize ignoresValueChanges;
@synthesize ignoresTransferChange;

- (long long)total {
    NSNumber * tmpValue;
    [self willAccessValueForKey:@"total"];
    tmpValue = [self primitiveTotal];
    [self didAccessValueForKey:@"total"];
    return (tmpValue == nil) ? 0 : [tmpValue longLongValue];
}
- (void)setTotal:(long long)total {
	NSNumber *temp = [[NSNumber alloc] initWithLongLong:total];
    [self willChangeValueForKey:@"total"];
    [self setPrimitiveTotal:temp];
    [self didChangeValueForKey:@"total"];
	[temp release];
}

- (CBTransaction *)transfer {
    CBTransaction *tmpValue;
    [self willAccessValueForKey:@"transfer"];
    tmpValue = [self primitiveTransfer];
    [self didAccessValueForKey:@"transfer"];
    return tmpValue;
}

- (void)setTransfer:(CBTransaction *)transaction {
	
	BOOL ignores = [transaction ignoresTransferChange];
	[transaction setIgnoresTransferChange:TRUE];
	
	// only proceed when the call is made on the first transaction.
	// setting the primitive value (below) will lead to the call
	// being made a second time to set up the relationship
	// between the two transactions.
	if (![self ignoresTransferChange] && transaction) {
		NSEnumerator *attributesEnumerator = [[[[transaction class] attributeInfo] attributes] objectEnumerator];
		CBAttribute *attribute;
		while (attribute = [attributesEnumerator nextObject]) {
			if ([attribute links] != CBNoRole) {
				id value = [transaction valueForRoleType:[attribute links]];
				if (value && [value isKindOfClass:[NSNumber class]] && [value longLongValue] == 0) { value = nil; }
				if (value) {
					[CBInvalidTransferSetupException raise:@"InvalidTransferSetupException"
													format:@"Transfer transaction %@ should not be set "
															"when initially setting up the transfer",
															[attribute name]];
				}
			}
		}
	}
	
    [self willChangeValueForKey:@"transfer"];
    [self setPrimitiveTransfer:transaction];
    [self didChangeValueForKey:@"transfer"];

	if (![self ignoresTransferChange]) {
		CBAttribute *attribute;
		NSEnumerator *attributesEnumerator = [[[[self class] attributeInfo] attributes] objectEnumerator];
		while (attribute = [attributesEnumerator nextObject]) {
			[self setTransferValue:[self valueForKey:[attribute name]] forKey:[attribute name]];
		}
	}

	[transaction setIgnoresTransferChange:ignores];
}


- (CBAttribute *)attributeForRoleType:(CBRoleType)role {
	CBAttribute *attribute = nil;
	if (role != CBNoRole) {
		NSEnumerator *attributeEnumerator = [[[[self class] attributeInfo] attributes] objectEnumerator];
		while ((attribute = [attributeEnumerator nextObject])) {
			if (role == [attribute role]) { break; }
		}
	}
	return attribute;
}

- (id)valueForRoleType:(CBRoleType)role {
	CBAttribute *attribute = [self attributeForRoleType:role];
	if (attribute) { return [self valueForKey:[attribute name]]; }
	else { return nil; }
}

- (void)setValue:(id)value forRole:(CBRoleType)role {
	CBAttribute *attribute = [self attributeForRoleType:role];
	if (attribute) { [self setValue:value forKey:[attribute name]]; }
}


@end
